package com.nulldev.util.internal.backport.httpclient_rw.httpclient;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.List;

import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.internal.backport.concurrency9.concurrent.CompletionStage;
import com.nulldev.util.internal.backport.httpclient_rw.WebSocket;
import com.nulldev.util.web.HttpClient.providers.websockets.HttpWebSocket;
import com.nulldev.util.web.HttpClient.providers.websockets.WebSocketListener;
import com.nulldev.util.web.HttpClient.providers.websockets.WebSocketListener.Message;
import com.nulldev.util.internal.backport.httpclient_rw.WebSocket.Listener;
import com.nulldev.util.internal.backport.httpclient_rw.WebSocket.Builder;

public class BackportedWebSocket implements HttpWebSocket, Listener {

	private final WebSocket socket;
	private final List<WebSocketListener> listeners;

	public BackportedWebSocket(final Builder builder, final URI url) throws Exception {
		this.listeners = FastLists.list();
		this.socket = builder.buildAsync(url, this).join();
	}

	@Override
	public void close() throws IOException {
		try {
			this.socket.sendClose(1000, "User requested close.").join();
		} catch (Exception e) {
			throw new IOException(e);
		}
	}

	@Override
	public void send(String msg) {
		this.socket.sendText(msg, false).join();
	}

	@Override
	public void send(byte[] msg) {
		this.socket.sendBinary(ByteBuffer.wrap(msg), false).join();
	}

	@Override
	public void send(String msg, WebSocketMessageType type) {
		switch (type) {
			case BINARY:
				this.socket.sendBinary(ByteBuffer.wrap(msg.getBytes()), false).join();
				break;
			case TEXT:
				this.socket.sendText(msg, false).join();
				break;
			default:
				throw new UnsupportedOperationException("Unknown type: " + type);
		}
	}

	@Override
	public void send(byte[] msg, WebSocketMessageType type) {
		switch (type) {
			case BINARY:
				this.socket.sendBinary(ByteBuffer.wrap(msg), false).join();
				break;
			case TEXT:
				this.socket.sendText(new String(msg), false).join();
				break;
			default:
				throw new UnsupportedOperationException("Unknown type: " + type);
		}
	}

	@Override
	public boolean registerListener(WebSocketListener listener) {
		if (this.isOpen)
			listener.onConnect();
		return this.listeners.add(listener);
	}

	private volatile boolean isOpen;

	@Override
	public void onOpen(WebSocket webSocket) {
		this.isOpen = true;
		this.listeners.forEach((list) -> {
			list.onConnect();
		});
		Listener.super.onOpen(webSocket);
	}

	@Override
	public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
		this.isOpen = false;
		this.listeners.forEach((list) -> {
			list.onClosed();
		});
		return Listener.super.onClose(webSocket, statusCode, reason);
	}

	@Override
	public void onError(WebSocket webSocket, Throwable error) {
		this.listeners.forEach((list) -> {
			list.onError(error);
		});
		Listener.super.onError(webSocket, error);
	}

	@Override
	public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer message, boolean last) {
		this.listeners.forEach((list) -> {
			list.onMessage(new Message(message.array(), WebSocketMessageType.BINARY)); // TODO: Handle MessagePart(s).
		});
		return Listener.super.onBinary(webSocket, message, last);
	}

	@Override
	public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
		this.listeners.forEach((list) -> {
			list.onMessage(new Message(message.toString(), WebSocketMessageType.TEXT)); // TODO: Handle MessagePart(s).
		});
		return Listener.super.onText(webSocket, message, last);
	}

}
